/**
 * 策略模式是指定义一系列的算法，把它们单独封装起来，并且使它们可以互相替换，使得算法可以独立于使用它的客户端而变化，也是说这些算法所完成的功能类型是一样的，对外接口也是一样的，只是不同的策略为引起环境角色环境角色表现出不同的行为。
 * 相比于使用大量的if...else，使用策略模式可以降低复杂度，使得代码更容易维护。
 * 策略模式适合应用场景:
 * 1. 当想使用对象中各种不同的算法变体，并希望能在运行时切换算法时，可使用策略模式。
 * 2. 当有许多仅在执行某些行为时略有不同的相似类时，可使用策略模式。
 * 3. 如果算法在上下文的逻辑中不是特别重要，使用该模式能将类的业务逻辑与其算法实现细节隔离开来。
 * 4. 当类中使用了复杂条件运算符以在同一算法的不同变体中切换时，可使用该模式。
 * 缺点：可能需要定义大量的策略类，并且这些策略类都要提供给客户端。
 */
#pragma once

#include <iostream>
#include <vector>
#include <memory>

using namespace std;

namespace strategy
{
class Strategy
{
public:
    virtual string DoAlgorithm(const vector<string>&) const = 0;
};

// Context定义了客户端感兴趣的接口。
class Context
{
    /**
     * 通常，Context 通过构造函数接受一个策略，但也提供了一个 setter 来在运行时更改它。
     */
public:
    Context(Strategy *strategy = nullptr) : m_strategy(strategy)
    {
    }
    ~Context()
    {
        if (m_strategy)
            delete m_strategy;
    }
    /**
     * 通常，Context 允许在运行时替换策略对象。
     */
    void setStrategy(Strategy *strategy)
    {
        delete m_strategy;
        m_strategy = strategy;
    }
    /**
     * Context 将一些工作委托给 Strategy 对象，而不是自己实现算法的多个版本。
     */
    void DoSomeBusinessLogic() const
    {
        cout << "Context: Sorting data using the strategy (not sure how it'll do it)" << endl;
        const string& result = m_strategy->DoAlgorithm(vector<string>{"a", "e", "c", "b", "d"});
        cout << result << endl;
    }

    /**
     * Context 维护一个对策略对象的引用。Context 不知道一个策略的具体类别。它应该通过策略接口与所有策略一起工作。
     */
private:
    Strategy *m_strategy;
};

/**
 * 具体策略在遵循基本策略接口的同时实现算法。接口使它们在 Context 中可以互换。
 */
class ConcreteStrategyA : public Strategy
{
public:
    string DoAlgorithm(const vector<string>& data) const override
    {
        string result;
        for_each(begin(data), end(data), [&result] (const string& letter) {
            result += letter;
        });
        sort(begin(result), end(result));
        return result;
    }
};
class ConcreteStrategyB : public Strategy
{
    string DoAlgorithm(const vector<string>& data) const override
    {
        string result;
        for_each(begin(data), end(data), [&result](const string& letter) {
            result += letter;
        });
        sort(begin(result), end(result));
        for (int i = 0; i < result.size() / 2; i++)
        {
            swap(result[i], result[result.size() - i - 1]);
        }
        return result;
    }
};

void ClientCode()
{
    unique_ptr<Context> context(new Context(new ConcreteStrategyA));
    cout << "Client: Strategy is set to normal sorting.\n";
    context->DoSomeBusinessLogic();
    cout << endl;
    cout << "Client: Strategy is set to reverse sorting.\n";
    context->setStrategy(new ConcreteStrategyB);
    context->DoSomeBusinessLogic();
}
}   // namespace strategy